/*******************************************************************************************
This file is part of iToday.

iToday is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

iToday is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with iToday.  If not, see <http://www.gnu.org/licenses/>.
* ------------------------------------------------------------------------------
*    File.cpp - implement a MFC-like File Utility Class
* ------------------------------------------------------------------------------
*    Revision History
*    Date                  Author         Activity ID               Activity Headline
*    2010-03-19            CubeWang				none						initial 
*******************************************************************************************/

#include "stdafx.h"
#include "File.h"

using ATL::CTime;

void GetRoot(LPCTSTR lpszPath, CString& strRoot)
{
	ASSERT(lpszPath != NULL);

	LPTSTR lpszRoot = strRoot.GetBuffer(_MAX_PATH);
	memset(lpszRoot, 0, _MAX_PATH);
	Checked::tcsncpy_s(lpszRoot, _MAX_PATH, lpszPath, _TRUNCATE);
	strRoot.ReleaseBuffer();
}

// IsValidString() returns TRUE if the passed pointer
// references a string of at least the given length in characters.
// A length of -1 (the default parameter) means that the string
// buffer's minimum length isn't known, and the function will
// return TRUE no matter how long the string is. The memory
// used by the string can be read-only.
BOOL IsValidString(LPCWSTR psz, int nLength  = -1 )
{
	return ATL::AtlIsValidString(psz, nLength);
}

// IsValidAddress() returns TRUE if the passed parameter points
// to at least nBytes of accessible memory. If bReadWrite is TRUE,
// the memory must be writeable; if bReadWrite is FALSE, the memory
// may be const.
BOOL IsValidAddress(const void* p, UINT_PTR nBytes,
					BOOL bReadWrite = TRUE )
{
	return ATL::AtlIsValidAddress(p, nBytes, bReadWrite);
}


void TimeToFileTime(const CTime& time, LPFILETIME pFileTime)
{
	ASSERT(pFileTime != NULL);

	if (pFileTime == NULL) 
	{
		throw L"Invalidate";
	}

	SYSTEMTIME sysTime;
	sysTime.wYear = (WORD)time.GetYear();
	sysTime.wMonth = (WORD)time.GetMonth();
	sysTime.wDay = (WORD)time.GetDay();
	sysTime.wHour = (WORD)time.GetHour();
	sysTime.wMinute = (WORD)time.GetMinute();
	sysTime.wSecond = (WORD)time.GetSecond();
	sysTime.wMilliseconds = 0;

	// convert system time to local file time
	FILETIME localTime;
	if (!SystemTimeToFileTime((LPSYSTEMTIME)&sysTime, &localTime))
		throw L"System time to file Time failed!";

	// convert local file time to UTC file time
	if (!LocalFileTimeToFileTime(&localTime, pFileTime))
		throw L"Local file time to file time failed!";
}

// turn a CFile, relative path or other into an absolute path
BOOL FullPath(__out_ecount_z(_MAX_PATH) LPTSTR lpszPathOut, LPCTSTR lpszFileIn)
// lpszPathOut = buffer of _MAX_PATH
// lpszFileIn = CFile, relative path or absolute path
// (both in ANSI character set)
{
	ASSERT(IsValidString(lpszPathOut));
	ASSERT(IsValidString(lpszFileIn));
	ASSERT(IsValidAddress(lpszPathOut, _MAX_PATH));

	// first, fully qualify the path name
	LPTSTR lpszFilePart;
	DWORD dwRet = GetFullPathName(lpszFileIn, _MAX_PATH, lpszPathOut, &lpszFilePart);
	if (dwRet == 0)
	{
		Checked::tcsncpy_s(lpszPathOut, _MAX_PATH, lpszFileIn, _TRUNCATE); // take it literally
		return FALSE;
	}
	else if (dwRet >= _MAX_PATH)
	{
		return FALSE; // long path won't fit in buffer
	}

	CString strRoot;
	// determine the root name of the volume
	GetRoot(lpszPathOut, strRoot);

	return TRUE;
}


CFile::CFile()
{
	m_hFile = INVALID_HANDLE_VALUE;
	//m_bCloseOnDelete = FALSE;
}

CFile::CFile(HANDLE hFile)
{
	ASSERT(hFile != INVALID_HANDLE_VALUE);
#if defined(_DEBUG) && !defined(_WIN32_WCE) // GetHandleInformation
	DWORD dwFlags=0;
	ASSERT(GetHandleInformation(hFile,&dwFlags) != 0 );
#endif
	m_hFile = hFile;
	//m_bCloseOnDelete = FALSE;
}

CFile::CFile(LPCTSTR lpszFileName, UINT nOpenFlags)
{
	ASSERT(::IsValidString(lpszFileName));
	m_hFile = INVALID_HANDLE_VALUE;

	if (!Open(lpszFileName, nOpenFlags))
		throw L"Open file failed";
}

CFile::~CFile()
{
	if (m_hFile != INVALID_HANDLE_VALUE && m_bCloseOnDelete)
		Close();
}


BOOL CFile::Open(LPCTSTR lpszFileName, UINT nOpenFlags)
{
	//ASSERT_VALID(this);
	ASSERT(::IsValidString(lpszFileName));

	ASSERT((nOpenFlags & typeText) == 0);   // text mode not supported

	// shouldn't open an already open CFile (it will leak)
	ASSERT(m_hFile == INVALID_HANDLE_VALUE);

	// CFile objects are always binary and CreateFile does not need flag
	nOpenFlags &= ~(UINT)typeBinary;

	m_bCloseOnDelete = FALSE;

	m_hFile = INVALID_HANDLE_VALUE;
	m_strFileName.Empty();

	TCHAR szTemp[_MAX_PATH];
	if (lpszFileName != NULL && ATL::lstrlen(lpszFileName) < _MAX_PATH)
	{
		if( FullPath(szTemp, lpszFileName) == FALSE )
			return FALSE;
	}
	else
		return FALSE; // path is too long

	m_strFileName = szTemp;
	ASSERT(shareCompat == 0);

	// map read/write mode
	ASSERT((modeRead|modeWrite|modeReadWrite) == 3);
	DWORD dwAccess = 0;
	switch (nOpenFlags & 3)
	{
	case modeRead:
		dwAccess = GENERIC_READ;
		break;
	case modeWrite:
		dwAccess = GENERIC_WRITE;
		break;
	case modeReadWrite:
		dwAccess = GENERIC_READ | GENERIC_WRITE;
		break;
	default:
		ASSERT(FALSE);  // invalid share mode
	}

	// map share mode
	DWORD dwShareMode = 0;
	switch (nOpenFlags & 0x70)    // map compatibility mode to exclusive
	{
	default:
		ASSERT(FALSE);  // invalid share mode?
	case shareCompat:
	case shareExclusive:
		dwShareMode = 0;
		break;
	case shareDenyWrite:
		dwShareMode = FILE_SHARE_READ;
		break;
	case shareDenyRead:
		dwShareMode = FILE_SHARE_WRITE;
		break;
	case shareDenyNone:
		dwShareMode = FILE_SHARE_WRITE | FILE_SHARE_READ;
		break;
	}

	// Note: typeText and typeBinary are used in derived classes only.

	// map modeNoInherit flag
	SECURITY_ATTRIBUTES sa;
	sa.nLength = sizeof(sa);
	sa.lpSecurityDescriptor = NULL;
	sa.bInheritHandle = (nOpenFlags & modeNoInherit) == 0;

	// map creation flags
	DWORD dwCreateFlag;
	if (nOpenFlags & modeCreate)
	{
		if (nOpenFlags & modeNoTruncate)
			dwCreateFlag = OPEN_ALWAYS;
		else
			dwCreateFlag = CREATE_ALWAYS;
	}
	else
		dwCreateFlag = OPEN_EXISTING;

	// special system-level access flags

	// Random access and sequential scan should be mutually exclusive
	ASSERT((nOpenFlags&(osRandomAccess|osSequentialScan)) != (osRandomAccess|
		osSequentialScan) );

	DWORD dwFlags = FILE_ATTRIBUTE_NORMAL;
	if (nOpenFlags & osNoBuffer)
		dwFlags |= FILE_FLAG_NO_BUFFERING;
	if (nOpenFlags & osWriteThrough)
		dwFlags |= FILE_FLAG_WRITE_THROUGH;
	if (nOpenFlags & osRandomAccess)
		dwFlags |= FILE_FLAG_RANDOM_ACCESS;
	if (nOpenFlags & osSequentialScan)
		dwFlags |= FILE_FLAG_SEQUENTIAL_SCAN;

	// attempt CFile creation
	HANDLE hFile = ::CreateFile(lpszFileName, dwAccess, dwShareMode, &sa,
		dwCreateFlag, dwFlags, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
	{
		//throw L"Create CFile failed!\n";
		//printf("Create CFile failed!\n");
		return FALSE;
	}
	m_hFile = hFile;
	m_bCloseOnDelete = TRUE;

	return TRUE;
}


UINT CFile::Read(void* lpBuf, UINT nCount)
{
	//ASSERT_VALID(this);
	ASSERT(m_hFile != INVALID_HANDLE_VALUE);

	if (nCount == 0)
		return 0;   // avoid Win32 "null-read"

	ASSERT(lpBuf != NULL);
	ASSERT(::IsValidAddress(lpBuf, nCount));

	DWORD dwRead;
	if (!::ReadFile(m_hFile, lpBuf, nCount, &dwRead, NULL))
		//throw L"Read file failed!";
		PrintDebugString(L"CFile::Read: Read file failed! Error code is %d.\n", GetLastError());

	return (UINT)dwRead;
}

void CFile::Write(const void* lpBuf, UINT nCount)
{
	//ASSERT_VALID(this);
	ASSERT(m_hFile != INVALID_HANDLE_VALUE);

	if (nCount == 0)
		return;     // avoid Win32 "null-write" option

	ASSERT(lpBuf != NULL);
	ASSERT(::IsValidAddress(lpBuf, nCount, FALSE));

	DWORD nWritten;
	if (!::WriteFile(m_hFile, lpBuf, nCount, &nWritten, NULL))
		//throw L"Write CFile failed!\n";
		PrintDebugString(L"CFile::Write: Write file failed! Error code is %d.\n", GetLastError());
	//// Win95 will not return an error all the time (usually DISK_FULL)
	//if (nWritten != nCount)
	//	throw L"Write CFile failed!\n";
}

ULONGLONG CFile::Seek(LONGLONG lOff, UINT nFrom)
{
	ASSERT(m_hFile != INVALID_HANDLE_VALUE);
	ASSERT(nFrom == begin || nFrom == end || nFrom == current);
	ASSERT(begin == FILE_BEGIN && end == FILE_END && current == FILE_CURRENT);

	LARGE_INTEGER liOff;

	liOff.QuadPart = lOff;
	liOff.LowPart = ::SetFilePointer(m_hFile, liOff.LowPart, NULL, (DWORD)nFrom);
	if (liOff.LowPart  == (DWORD)-1)
		if (::GetLastError() != NO_ERROR)
			//throw L"";
			PrintDebugString(L"CFile::Seek: Error occurred! Error code is %d.\n", GetLastError());

	return liOff.QuadPart;
}

ULONGLONG CFile::GetPosition() const
{
	ASSERT(m_hFile != INVALID_HANDLE_VALUE);

	LARGE_INTEGER liPos;
	liPos.QuadPart = 0;
	liPos.LowPart = ::SetFilePointer(m_hFile, liPos.LowPart, NULL, FILE_CURRENT);
	if (liPos.LowPart == (DWORD)-1)
		if (::GetLastError() != NO_ERROR)
			//throw L"";
			PrintDebugString(L"CFile::GetPosition: Error occurred! Error code is %d.\n", GetLastError());

	return liPos.QuadPart;
}

void CFile::Flush()
{
	if (m_hFile == INVALID_HANDLE_VALUE)
		return;

	if (!::FlushFileBuffers(m_hFile))
		//throw L"Flush file buffers failed!";
		PrintDebugString(L"CFile::Flush: Flush file buffers failed! Error code is %d.\n", GetLastError());
}

void CFile::Close()
{
	ASSERT(m_hFile != INVALID_HANDLE_VALUE);
	BOOL bError = FALSE;
	if (m_hFile != INVALID_HANDLE_VALUE)
		bError = !::CloseHandle(m_hFile);

	m_hFile = INVALID_HANDLE_VALUE;
	m_bCloseOnDelete = FALSE;
	m_strFileName.Empty();

	if (bError)
		//throw L"Close handle failed!";
		PrintDebugString(L"CFile::Close: Close handle failed! Error code is %d.\n", GetLastError());
}


void CFile::SetLength(ULONGLONG dwNewLen)
{
	ASSERT(m_hFile != INVALID_HANDLE_VALUE);

	Seek(dwNewLen, (UINT)begin);

	if (!::SetEndOfFile(m_hFile))
		//throw L"Set end of file failed!";
		PrintDebugString(L"CFile::SetLength: Set end of file failed! Error code is %d.\n", GetLastError());
}

ULONGLONG CFile::GetLength() const
{
	ULARGE_INTEGER liSize;
	liSize.LowPart = ::GetFileSize(m_hFile, &liSize.HighPart);
	if (liSize.LowPart == INVALID_FILE_SIZE)
		if (::GetLastError() != NO_ERROR)
			//throw L"Error occurred!";
			PrintDebugString(L"CFile::GetLength: Error occurred! Error code is %d.\n", GetLastError());

	return liSize.QuadPart;
}

void CFile::Rename(LPCTSTR lpszOldName, LPCTSTR lpszNewName)
{
	if (!::MoveFile((LPTSTR)lpszOldName, (LPTSTR)lpszNewName))
		//throw L"Move file failed!";
		PrintDebugString(L"CFile::Rename: Rename the file failed! Error code is %d.\n", GetLastError());
}

void CFile::Remove(LPCTSTR lpszFileName)
{
	if (!::DeleteFile((LPTSTR)lpszFileName))
		//throw L"Delete file failed!";
		PrintDebugString(L"CFile::Remove: Remove the file failed! Error code is %d.\n", GetLastError());
}

CString CFile::GetFileName() const
{
	CFileStatus status;
	GetStatus(status);
	CString strResult;

	LPTSTR lpszTitle = strResult.GetBuffer(_MAX_FNAME);
	// always capture the complete file name including extension (if present)
	LPTSTR lpszTemp = ::PathFindFileName(status.m_szFullName);

	// otherwise copy it into the buffer provided
	Checked::tcsncpy_s(lpszTitle, _MAX_FNAME, lpszTemp, _TRUNCATE);

	strResult.ReleaseBuffer();
	return strResult;
}

/////////////////////////////////////////////////////////////////////////////
// CFile Status implementation

BOOL CFile::GetStatus(CFileStatus& rStatus) const
{
	memset(&rStatus, 0, sizeof(CFileStatus));

	// copy file name from cached m_strFileName
	Checked::tcsncpy_s(rStatus.m_szFullName, _countof(rStatus.m_szFullName), m_strFileName, _TRUNCATE);

	if (INVALID_HANDLE_VALUE != m_hFile)
	{
		// get time current file size
		FILETIME ftCreate, ftAccess, ftModify;
		if (!::GetFileTime(m_hFile, &ftCreate, &ftAccess, &ftModify))
			return FALSE;

		if ((rStatus.m_size = ::GetFileSize(m_hFile, NULL)) == INVALID_FILE_SIZE)
			return FALSE;

		if (m_strFileName.IsEmpty())
			rStatus.m_attribute = 0;
		else
		{
			DWORD dwAttribute = ::GetFileAttributes(m_strFileName);

			// don't return an error for this because previous versions of MFC didn't
			if (dwAttribute == 0xFFFFFFFF)
				rStatus.m_attribute = 0;
			else
			{
				rStatus.m_attribute = (BYTE) dwAttribute;
			}
		}

		// convert times as appropriate
		// some file systems may not record file creation time, file access time etc
		if (CTime::IsValidFILETIME(ftCreate))
		{
			rStatus.m_ctime = CTime(ftCreate);
		}
		else
		{
			rStatus.m_ctime = CTime();
		}

		if (CTime::IsValidFILETIME(ftAccess))
		{
			rStatus.m_atime = CTime(ftAccess);
		}
		else
		{
			rStatus.m_atime = CTime();
		}

		if (CTime::IsValidFILETIME(ftModify))
		{
			rStatus.m_mtime = CTime(ftModify);
		}
		else
		{
			rStatus.m_mtime = CTime();
		}

		if (rStatus.m_ctime.GetTime() == 0)
			rStatus.m_ctime = rStatus.m_mtime;

		if (rStatus.m_atime.GetTime() == 0)
			rStatus.m_atime = rStatus.m_mtime;
	}
	return TRUE;
}

BOOL CFile::GetStatus(LPCTSTR lpszFileName, CFileStatus& rStatus)
{
	ASSERT( lpszFileName != NULL );

	if ( lpszFileName == NULL ) 
	{
		return FALSE;
	}

	if ( ATL::lstrlen(lpszFileName) >= _MAX_PATH )
	{
		ASSERT(FALSE); // MFC requires paths with length < _MAX_PATH
		return FALSE;
	}

	// attempt to fully qualify path first
	if (!FullPath(rStatus.m_szFullName, lpszFileName))
	{
		rStatus.m_szFullName[0] = '\0';
		return FALSE;
	}

	WIN32_FIND_DATA findFileData;
	HANDLE hFind = FindFirstFile((LPTSTR)lpszFileName, &findFileData);
	if (hFind == INVALID_HANDLE_VALUE)
		return FALSE;
	VERIFY(FindClose(hFind));

	// strip attribute of NORMAL bit, our API doesn't have a "normal" bit.
	rStatus.m_attribute = (BYTE)
		(findFileData.dwFileAttributes & ~FILE_ATTRIBUTE_NORMAL);

	// get just the low DWORD of the file size
	ASSERT(findFileData.nFileSizeHigh == 0);
	rStatus.m_size = (LONG)findFileData.nFileSizeLow;

	// convert times as appropriate
	if (CTime::IsValidFILETIME(findFileData.ftCreationTime))
	{
		rStatus.m_ctime = CTime(findFileData.ftCreationTime);
	}
	else
	{
		rStatus.m_ctime = CTime();
	}

	if (CTime::IsValidFILETIME(findFileData.ftLastAccessTime))
	{
		rStatus.m_atime = CTime(findFileData.ftLastAccessTime);
	}
	else
	{
		rStatus.m_atime = CTime();
	}

	if (CTime::IsValidFILETIME(findFileData.ftLastWriteTime))
	{
		rStatus.m_mtime = CTime(findFileData.ftLastWriteTime);
	}
	else
	{
		rStatus.m_mtime = CTime();
	}

	if (rStatus.m_ctime.GetTime() == 0)
		rStatus.m_ctime = rStatus.m_mtime;

	if (rStatus.m_atime.GetTime() == 0)
		rStatus.m_atime = rStatus.m_mtime;

	return TRUE;
}

void CFile::SetStatus(LPCTSTR lpszFileName, const CFileStatus& status)
{
	DWORD wAttr;
	FILETIME creationTime;
	FILETIME lastAccessTime;
	FILETIME lastWriteTime;
	LPFILETIME lpCreationTime = NULL;
	LPFILETIME lpLastAccessTime = NULL;
	LPFILETIME lpLastWriteTime = NULL;

	if ((wAttr = GetFileAttributes((LPTSTR)lpszFileName)) == (DWORD)-1L)
		//throw L"Get file attributes failed!";
		PrintDebugString(L"CFile::SetStatus: Get file attributes failed! Error code is %d.\n", GetLastError());

	if ((DWORD)status.m_attribute != wAttr && (wAttr & readOnly))
	{
		// Set file attribute, only if currently readonly.
		// This way we will be able to modify the time assuming the
		// caller changed the file from readonly.

		if (!SetFileAttributes((LPTSTR)lpszFileName, (DWORD)status.m_attribute))
			//throw L"Set file attributes failed!";
			PrintDebugString(L"CFile::SetStatus: Set file attributes failed! Error code is %d.\n", GetLastError());
	}

	// last modification time
	if (status.m_mtime.GetTime() != 0)
	{
		::TimeToFileTime(status.m_mtime, &lastWriteTime);
		lpLastWriteTime = &lastWriteTime;

		// last access time
		if (status.m_atime.GetTime() != 0)
		{
			::TimeToFileTime(status.m_atime, &lastAccessTime);
			lpLastAccessTime = &lastAccessTime;
		}

		// create time
		if (status.m_ctime.GetTime() != 0)
		{
			::TimeToFileTime(status.m_ctime, &creationTime);
			lpCreationTime = &creationTime;
		}

		HANDLE hFile = ::CreateFile(lpszFileName, GENERIC_READ|GENERIC_WRITE,
			FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
			NULL);

		if (hFile == INVALID_HANDLE_VALUE)
			//throw L"hfile is INVALID_HANDLE_VALUE!";
			PrintDebugString(L"CFile::SetStatus: hfile is INVALID_HANDLE_VALUE!\n");

		if (!SetFileTime((HANDLE)hFile, lpCreationTime, lpLastAccessTime, lpLastWriteTime))
			//throw L"Set file time failed!";
			PrintDebugString(L"CFile::SetStatus: Set file time failed! Error code is %d.\n", GetLastError());

		if (!::CloseHandle(hFile))
			//throw L"Close handle hfile failed!";
			PrintDebugString(L"CFile::SetStatus: Close the handle of hFile failed! Error code is %d.\n", GetLastError());
	}

	if ((DWORD)status.m_attribute != wAttr && !(wAttr & readOnly))
	{
		if (!SetFileAttributes((LPTSTR)lpszFileName, (DWORD)status.m_attribute))
			//throw L"Set file attributes failed!";
			PrintDebugString(L"CFile::SetStatus: Set file attributes failed! Error code is %d.\n", GetLastError());
	}
}

CString CFile::GetFilePath() const
{
	CFileStatus status;
	GetStatus(status);
	return status.m_szFullName;
}
